/*
 * @Author: 李石
 * @Date: 2024-04-08 09:47:34
 * @LastEditors: lishi
 * @LastEditTime: 2024-07-18 17:55:34
 * @Description: 
 * Copyright (c) 2024 by ${lishi0105@163.com}, All Rights Reserved. 
 */
#ifndef _LOGGER_HPP_
#define _LOGGER_HPP_
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
#include "spdlog/spdlog.h"
#include <chrono>
#include <dirent.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <thread>
#include <mutex>
#include <regex>
#include <time.h>
#include <fmt/format.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "spdlog/details/null_mutex.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"

// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE //必须定义这个宏,才能输出文件名和行号

static bool customCompare(const std::string& a, const std::string& b) 
{
    std::regex re("_(\\d+)_"); // 用于查找文件名中的数字
    std::smatch matchA, matchB;

    // 提取文件名中的数字
    if (std::regex_search(a, matchA, re) && std::regex_search(b, matchB, re)) {
        // 按数字的大小进行排序
        return std::stoi(matchA[1]) < std::stoi(matchB[1]);
    }

    // 如果没有找到数字，或者两个文件名相同，回退到字典序
    return a < b;
}

class DailyRotatingFileSizeLimitedSink
    : public spdlog::sinks::base_sink<spdlog::details::null_mutex> {
public:
    DailyRotatingFileSizeLimitedSink(const std::string& log_path,
                                     const std::string& base_filename,
                                     size_t max_size_bytes, size_t max_files)
        : log_path_(log_path),
          base_filename_(base_filename),
          max_size_bytes_(max_size_bytes),
          max_files_(max_files) {
        log_flag_ = get_log_flag();
        calc_filename();
        create_directory_recursive(log_path);
        open_new_file();
    }
    ~DailyRotatingFileSizeLimitedSink() {
        bExit = true;
    }
protected:
    void sink_it_(const spdlog::details::log_msg& msg) override {
        // Check if file size exceeds max size
        if (current_file_size_ >= max_size_bytes_ || is_new_day()) {
            // Close current file and create new file
            if (file_.is_open()) {
                file_.close();
            }
            current_file_index_++;
            if(current_file_index_ > 99)current_file_index_ = 1;
            current_file_size_ = 0;
            calc_filename();
            open_new_file();
        }

        // Write log message to file
        if (file_.is_open()) {
            spdlog::memory_buf_t formatted;
            base_sink<spdlog::details::null_mutex>::formatter_->format(msg, formatted);
            file_ << fmt::format("{}", fmt::to_string(formatted));
            current_file_size_ += fmt::to_string(formatted).size();
        }
    }

    void flush_() override {
        if (file_.is_open()) {
            file_.flush();
        }
    }

private:
     void create_directory_recursive(const std::string& path) {
        struct stat info;
        if (stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode)) {
            return ;
        }
        size_t pos = path.find_last_of('/');
        if (pos != std::string::npos) {
            std::string parent_path = path.substr(0, pos);
            create_directory_recursive(parent_path);
        }

        if (mkdir(path.c_str(), 0777) != 0) {
            throw std::runtime_error("Failed to create directory: " + path);
            return ;
        }
    }

    uint32_t get_log_flag()
    {
        uint32_t maxNumber = 0;
        if(log_path_.empty())return 0;
        std::string directoryPath = log_path_;
        DIR* dir;
        struct dirent* entry;

        if ((dir = opendir(directoryPath.c_str())) == NULL) {
            perror("opendir() error");
            return maxNumber;
        } else { 
            while ((entry = readdir(dir)) != NULL) {
                // 过滤掉"."和".."这两个特殊目录
                if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
                    continue;
                }
                uint32_t number;
                // 尝试从文件名中解析数字，格式为："cubeai-web_数字_其他.log"
                if (sscanf(entry->d_name, "%*[^_]_%u", &number) == 1) {
                    if (number > maxNumber) {
                        maxNumber = number;
                    }
                }
            }
        }
        closedir(dir);
        return (maxNumber+1);
    }

    void delete_files()
    {
        if(log_path_.empty())return;
        std::string directoryPath = log_path_;
        DIR* dir;
        struct dirent* entry;
        std::vector<std::string> fileNames;

        // 打开目录
        if ((dir = opendir(directoryPath.c_str())) == NULL) {
            perror("opendir() error");
        } else {
            // 读取目录内容
            while ((entry = readdir(dir)) != NULL) {
                uint32_t number = 0;
                if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
                    continue;
                }
                if (entry->d_type == DT_REG) { // 确保它是一个普通文件
                    if (sscanf(entry->d_name, "%*[^_]_%u", &number) == 1) {
                        fileNames.push_back(entry->d_name);
                    }
                }
            }
            closedir(dir);

            // 对文件名进行排序
            std::sort(fileNames.begin(), fileNames.end(), customCompare);

            // 如果文件总数大于5，删除多余的文件
            std::lock_guard<std::mutex> lock(file_mutex);
            int filesToDelete = fileNames.size() - 5;
            for (int i = 0; i < filesToDelete; ++i) {
                std::string fullPath = directoryPath + "/" + fileNames[i];
                if(filename_.compare(fullPath) == 0)continue;
                if (remove(fullPath.c_str()) != 0) {
                    perror("Error deleting file");
                } else {
                    std::cout << "Deleted file: " << fullPath << std::endl;
                }
            }
        }
    }

    std::string level_to_string(spdlog::level::level_enum lvl) {
        switch (lvl) {
            case spdlog::level::trace:
                return "TRACE";
            case spdlog::level::debug:
                return "DEBUG";
            case spdlog::level::info:
                return "INFO";
            case spdlog::level::warn:
                return "WARN";
            case spdlog::level::err:
                return "ERROR";
            case spdlog::level::critical:
                return "CRITICAL";
            case spdlog::level::off:
                return "OFF";
            default:
                return "UNKNOWN";
        }
    }

    bool is_new_day() {
        auto now = std::chrono::system_clock::now();
        auto now_date = std::chrono::system_clock::to_time_t(now);
        auto last_date = std::chrono::system_clock::to_time_t(last_log_time_);
        return std::difftime(now_date, last_date) >= 24 * 60 * 60;
    }

    void calc_filename() {
        std::stringstream ss;
        std::string directory = log_path_;
        if (directory.back() == '/') {
            directory.pop_back();
        }
        struct timeval tv;
        gettimeofday(&tv, NULL);
        struct tm* lt = localtime(&tv.tv_sec);
        std::string datetime = fmt::format("{:04d}-{:02d}-{:02d}-{:02d}-{:02d}-{:02d}", 
                                            lt->tm_year + 1900, 
                                            lt->tm_mon + 1, 
                                            lt->tm_mday, 
                                            lt->tm_hour, 
                                            lt->tm_min, 
                                            lt->tm_sec);
        std::lock_guard<std::mutex> lock(file_mutex);
        filename_ = fmt::format("{}/{}_{}_{:02}_{}.log", 
                                directory, 
                                base_filename_, 
                                log_flag_, 
                                current_file_index_, 
                                datetime);
    }

    void open_new_file() {
        // std::thread(&DailyRotatingFileSizeLimitedSink::delete_files, this);
        delete_files();
        file_.open(filename_, std::ofstream::out | std::ofstream::app);
        if (file_.fail()) {
            if(file_.is_open()) {
                file_.close();
            }
            std::cerr << "Failed to open [" << filename_ << "] Error: " << std::strerror(errno) << std::endl;
        }else{
            std::cout << "Opened new file: [" << filename_ << "] success"<< std::endl;
        }
    }

    std::string now_to_string(const std::chrono::system_clock::time_point& tp) {
        auto time = std::chrono::system_clock::to_time_t(tp);
        std::tm tm = *std::localtime(&time);
        std::stringstream ss;
        ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
        return ss.str();
    }

private:
    std::string log_path_;
    std::string base_filename_;
    uint32_t log_flag_;
    size_t max_size_bytes_;
    size_t max_files_;
    size_t current_file_index_{0};
    size_t current_file_size_{0};
    bool bExit;
    std::mutex file_mutex;
    std::ofstream file_;
    std::string filename_;
    std::chrono::system_clock::time_point last_log_time_{std::chrono::system_clock::now()};
};

class Logger {
public:
    static void init(const std::string& log_path, const std::string& base_name = "log", bool enable = true, int32_t level = 3, size_t max_size_bytes = 1048576, size_t max_files = 5) {
        auto log_level = spdlog::level::info;
        if(level == 0){
           log_level = spdlog::level::trace;
        }else if(level == 1){
            log_level = spdlog::level::debug;
        }else if(level == 2){
            log_level = spdlog::level::info;
        }else if(level == 3){
            log_level = spdlog::level::warn;
        }else if(level == 4){
            log_level = spdlog::level::err;
        }else if(level == 5){
            log_level = spdlog::level::critical;
        }else if(level == 6){
            log_level = spdlog::level::off;
        }
        if(enable){
            auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
            
            console_sink->set_level(log_level);
            console_sink->set_pattern("%^[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] [%s:%# %!] %v%$");
            // console_sink->set_color_mode(spdlog::color_mode::automatic);
        
            auto file_sink = std::make_shared<DailyRotatingFileSizeLimitedSink>(log_path, base_name, max_size_bytes, max_files);
            file_sink->set_level(log_level);
            file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] [%s:%#] %v");
            auto logger = std::make_shared<spdlog::logger>("multi_sink", spdlog::sinks_init_list{console_sink, file_sink});
            logger->set_level(log_level);
            logger->flush_on(spdlog::level::info);
            spdlog::register_logger(logger);
        }else{
            auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
            console_sink->set_level(log_level);
            console_sink->set_pattern("%^[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] [%s:%# %!] %v%$");
            // console_sink->set_color_mode(spdlog::color_mode::automatic);
            auto logger = std::make_shared<spdlog::logger>("multi_sink", spdlog::sinks_init_list{console_sink});
            logger->set_level(log_level);
            spdlog::register_logger(logger);
        }
        
    }
    static std::shared_ptr<spdlog::logger> get_logger() {
        return spdlog::get("multi_sink");
    }
public:
    static std::shared_ptr<spdlog::logger> logger_;
};
#define LOGI(...) SPDLOG_LOGGER_INFO(spdlog::get("multi_sink").get(), __VA_ARGS__)
#define LOGD(...) SPDLOG_LOGGER_DEBUG(spdlog::get("multi_sink").get(), __VA_ARGS__)
#define LOGW(...) SPDLOG_LOGGER_WARN(spdlog::get("multi_sink").get(), __VA_ARGS__)
#define LOGE(...) SPDLOG_LOGGER_ERROR(spdlog::get("multi_sink").get(), __VA_ARGS__)
#define LOGC(...) SPDLOG_LOGGER_CRITICAL(spdlog::get("multi_sink").get(), __VA_ARGS__)

#endif